﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace System.Collections.ObjectModel
{
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable"), Serializable]
	public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
	{
		const string _countString = "Count";
		const string _indexerName = "Item[]";
		readonly SimpleMonitor _monitor;

		public ObservableCollection()
		{
			_monitor = new SimpleMonitor();
		}

		public ObservableCollection(IEnumerable<T> collection)
		{
			_monitor = new SimpleMonitor();
			if (collection == null)
			{
				throw new ArgumentNullException("collection");
			}
			CopyFrom(collection);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
		public ObservableCollection(List<T> list) : base((list != null) ? new List<T>(list.Count) : list)
		{
			_monitor = new SimpleMonitor();
			CopyFrom(list);
		}

		#region INotifyCollectionChanged Members

		[field: NonSerialized]
		public event NotifyCollectionChangedEventHandler CollectionChanged;

		#endregion

		#region PropertyChanged

		[NonSerialized] PropertyChangedEventHandler _propertyChanged;

		event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
		{
			add { _propertyChanged += value; }
			remove { _propertyChanged -= value; }
		}

		protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
		{
			PropertyChangedEventHandler pc = _propertyChanged;
			if (pc != null)
			{
				pc(this, e);
			}
		}

		protected void OnPropertyChanged(string propertyName)
		{
			OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
		}

		#endregion

		protected IDisposable BlockReentrancy()
		{
			_monitor.Enter();
			return _monitor;
		}

		protected void CheckReentrancy()
		{
			if ((_monitor.Busy && (CollectionChanged != null)) && (CollectionChanged.GetInvocationList().Length > 1))
			{
				throw new InvalidOperationException(SR.GetString("ObservableCollectionReentrancyNotAllowed"));
			}
		}

		protected override void ClearItems()
		{
			CheckReentrancy();
			base.ClearItems();
			OnPropertyChanged("Count");
			OnPropertyChanged("Item[]");
			OnCollectionReset();
		}

		void CopyFrom(IEnumerable<T> collection)
		{
			var items = Items;
			if ((collection != null) && (items != null))
			{
				using (var enumerator = collection.GetEnumerator())
				{
					while (enumerator.MoveNext())
					{
						items.Add(enumerator.Current);
					}
				}
			}
		}

		protected override void InsertItem(int index, T item)
		{
			CheckReentrancy();
			base.InsertItem(index, item);
			OnPropertyChanged("Count");
			OnPropertyChanged("Item[]");
			OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
		}

		public void Move(int oldIndex, int newIndex)
		{
			MoveItem(oldIndex, newIndex);
		}

		protected virtual void MoveItem(int oldIndex, int newIndex)
		{
			CheckReentrancy();
			T item = base[oldIndex];
			base.RemoveItem(oldIndex);
			base.InsertItem(newIndex, item);
			OnPropertyChanged("Item[]");
			OnCollectionChanged(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex);
		}

		protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
		{
			if (CollectionChanged != null)
			{
				using (BlockReentrancy())
				{
					CollectionChanged(this, e);
				}
			}
		}

		void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
		{
			OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
		}

		void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex)
		{
			OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex));
		}

		void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index)
		{
			OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
		}

		void OnCollectionReset()
		{
			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
		}

		protected override void RemoveItem(int index)
		{
			CheckReentrancy();
			T item = base[index];
			base.RemoveItem(index);
			OnPropertyChanged("Count");
			OnPropertyChanged("Item[]");
			OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
		}

		protected override void SetItem(int index, T item)
		{
			CheckReentrancy();
			T oldItem = base[index];
			base.SetItem(index, item);
			OnPropertyChanged("Item[]");
			OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem, item, index);
		}

		// Nested Types

		#region Nested type: SimpleMonitor

		[Serializable]
		// TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
			class SimpleMonitor : IDisposable
		{
			// Fields
			[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2235:MarkAllNonSerializableFields")]
			int _busyCount;

			public bool Busy
			{
				get { return (_busyCount > 0); }
			}

			// Methods

			#region IDisposable Members

			[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
			public void Dispose()
			{
				_busyCount--;
			}

			#endregion

			public void Enter()
			{
				_busyCount++;
			}

			// Properties
		}

		#endregion
	}
}